#![allow(dead_code)]

use super::Tomlable;
use anyhow::anyhow;
use image::Rgba;
use serde::{Deserialize, Serialize};
// use std::collections::HashMap;
// use image::Rgba;
use std::collections::HashMap;
use std::convert::From;
use std::error::Error;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::str::FromStr;

pub const CAPTURE_DIR: &str = "captures";
pub const TRAIN_DIR: &str = "trains";
pub const PARSE_DIR: &str = "parses";

// #[derive(Debug, Clone, Serialize, Deserialize)]
// pub struct DevicePoint([u32;2]);

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DevicePoint(Vec<u32>);

// #[derive(Debug, Serialize, Deserialize)]
// struct Rect {
//   x: u32,
//   y: u32,
//   w: u32,
//   h: u32,
// }
// #[derive(Debug)]
// struct Point {
//   x: u32,
//   y: u32,
//   w: u32,
//   h: u32,
// }

impl DevicePoint {
  pub fn get_x(&self) -> u32 {
    return self.0[0];
  }
  pub fn get_y(&self) -> u32 {
    return self.0[1];
  }
}

impl From<Vec<u32>> for DevicePoint {
  fn from(v: Vec<u32>) -> Self {
    assert_eq!(v.len(), 2);
    DevicePoint(v)
  }
}

impl FromStr for DevicePoint {
  type Err = <u32 as std::str::FromStr>::Err;
  fn from_str(s: &str) -> Result<Self, Self::Err> {
    if s.is_empty() {
      Ok(Vec::new())
    } else {
      // let a = s.split(",").map(<u32 as std::str::FromStr>::from_str);
      // let b = a.collect::<std::result::Result<Vec<_>, Self::Err>>();
      // let c = b.map(<Self as std::convert::From<Vec<u32>>>::from);

      s.split(",")
        .map(<u32 as std::str::FromStr>::from_str)
        .collect::<std::result::Result<Vec<_>, Self::Err>>()
    }
    .map(<Self as From<Vec<u32>>>::from)
  }
}

// type DeviceRect = DeviceShape;
// type DeviceSwipeTrack = DeviceShape;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceShape(Vec<u32>);

impl From<Vec<u32>> for DeviceShape {
  fn from(v: Vec<u32>) -> Self {
    assert_eq!(v.len(), 4);
    DeviceShape(v)
  }
}

impl FromStr for DeviceShape {
  type Err = <u32 as std::str::FromStr>::Err;
  fn from_str(s: &str) -> Result<Self, Self::Err> {
    if s.is_empty() {
      Ok(Vec::new())
    } else {
      // let a = s.split(",").map(<u32 as std::str::FromStr>::from_str);
      // let b = a.collect::<std::result::Result<Vec<_>, Self::Err>>();
      // let c = b.map(<Self as std::convert::From<Vec<u32>>>::from);

      s.split(",")
        .map(<u32 as std::str::FromStr>::from_str)
        .collect::<std::result::Result<Vec<_>, Self::Err>>()
    }
    .map(<Self as From<Vec<u32>>>::from)
  }
}

pub fn parse_key_val<T, U>(s: &str) -> anyhow::Result<(T, U)>
where
  T: std::str::FromStr,
  T::Err: Error + 'static + Send + Sync,
  U: std::str::FromStr,
  U::Err: Error + 'static + Send + Sync,
{
  let s: &str = &(s.chars().filter(|c| !c.is_whitespace()).collect::<String>());
  // println!("s =>:{}", s);
  let pos = s
    .find('=')
    .ok_or_else(|| anyhow!("invalid KEY=value: no `=` found in `{}`", s))?;
  Ok((s[..pos].parse()?, s[pos + 1..].parse()?))
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ProjectConf {
  pub project: ProjectSection,
  pub app: AppSection,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ProjectSection {
  pub name: String,
  pub description: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct AppSection {
  pub name: String,
  pub capture_dir: PathBuf,
  pub train_dir: PathBuf,
  pub parse_dir: PathBuf,
  // device sn
  pub device: Option<String>,
  pub pages: Vec<AppPageSection>,
}

impl AppSection {
  pub fn new(name: String) -> Self {
    AppSection {
      name,
      capture_dir: PathBuf::from(CAPTURE_DIR),
      train_dir: PathBuf::from(TRAIN_DIR),
      parse_dir: PathBuf::from(PARSE_DIR),
      device: None,
      pages: vec![],
    }
  }

  pub fn get_page(&self, page_name: &str) -> Option<&AppPageSection> {
    self.pages.iter().find(|p| p.name == page_name)
  }

  pub fn set_page(&mut self, page: AppPageSection) {
    let pos = self.pages.iter().position(|p| p.name == page.name);
    if pos.is_none() {
      self.pages.push(page);
    } else {
      self.pages[pos.unwrap()] = page;
      // std::mem::replace(&mut self.pages[pos.unwrap()], page);
    }
  }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct AppPageSection {
  pub name: String,
  pub taps: Vec<AppPageTapSection>,
  // pub shapes: HashMap<String, DeviceShape>,
  pub capture: Option<AppPageCaptureSection>,
}

impl AppPageSection {
  pub fn new(
    name: String,
    taps: Vec<AppPageTapSection>,
    capture_items: HashMap<String, AppPageCaptureItemValue>,
  ) -> Self {
    AppPageSection {
      name,
      taps,
      capture: AppPageCaptureSection::from_hashmap(capture_items),
    }
  }

  pub fn get_tap(&self, tap_to: &str) -> Option<&AppPageTapSection> {
    self.taps.iter().find(|t| t.to == tap_to)
  }

  pub fn get_capture(&self) -> Option<&AppPageCaptureSection> {
    self.capture.as_ref()
  }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct AppPageTapSection {
  pub to: String,
  pub point: DevicePoint,
}

impl AppPageTapSection {
  pub fn new(to: String, point: DevicePoint) -> Self {
    AppPageTapSection { to, point }
  }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct AppPageCaptureSection {
  pub crop: Rect,
  pub swipe_from: Point,
  pub swipe_to: Point,
  pub swipe_duration: Mill,
  pub segment_border_color: Color,
  pub segment_sample_size: Size,
  pub segment_left_padding: Len,
  pub segment_right_padding: Len,
  pub segment_left_crop: Len,
  pub segment_right_crop: Len,
  pub segment_min_height: Len,
  pub segment_max_height: Len,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Rect(Option<[u32; 4]>);

#[derive(Debug, Serialize, Deserialize)]
pub struct Point(Option<[u32; 2]>);

#[derive(Debug, Serialize, Deserialize)]
pub struct Mill(Option<u32>);

#[derive(Debug, Serialize, Deserialize)]
pub struct Color(Option<[u8; 3]>);

#[derive(Debug, Serialize, Deserialize)]
pub struct Size(Option<u32>);

#[derive(Debug, Serialize, Deserialize)]
pub struct Len(Option<u32>);

impl From<Option<&AppPageCaptureItemValue>> for Rect {
  fn from(v: Option<&AppPageCaptureItemValue>) -> Self {
    if let Some(&AppPageCaptureItemValue::Rect { x, y, w, h }) = v {
      Rect(Some([x, y, w, h]))
    } else {
      Rect(None)
    }
  }
}
impl From<Option<&AppPageCaptureItemValue>> for Point {
  fn from(v: Option<&AppPageCaptureItemValue>) -> Self {
    if let Some(&AppPageCaptureItemValue::Point { x, y }) = v {
      Point(Some([x, y]))
    } else {
      Point(None)
    }
  }
}

impl From<Option<&AppPageCaptureItemValue>> for Mill {
  fn from(v: Option<&AppPageCaptureItemValue>) -> Self {
    if let Some(&AppPageCaptureItemValue::UInt(u)) = v {
      Mill(Some(u))
    } else {
      Mill(None)
    }
  }
}

impl From<Option<&AppPageCaptureItemValue>> for Color {
  fn from(v: Option<&AppPageCaptureItemValue>) -> Self {
    if let Some(&AppPageCaptureItemValue::Rgb { r, g, b }) = v {
      Color(Some([r, g, b]))
    } else {
      Color(None)
    }
  }
}

impl From<Option<&AppPageCaptureItemValue>> for Size {
  fn from(v: Option<&AppPageCaptureItemValue>) -> Self {
    if let Some(&AppPageCaptureItemValue::UInt(u)) = v {
      Size(Some(u))
    } else {
      Size(None)
    }
  }
}

impl From<Option<&AppPageCaptureItemValue>> for Len {
  fn from(v: Option<&AppPageCaptureItemValue>) -> Self {
    if let Some(&AppPageCaptureItemValue::UInt(u)) = v {
      Len(Some(u))
    } else {
      Len(None)
    }
  }
}

impl Point {
  pub fn shell_args(&self) -> String {
    match self.0 {
      Some([x, y]) => format!("{} {}", x, y),
      None => "0 0".to_owned(),
    }
  }
}

impl Mill {
  pub fn shell_args(&self) -> String {
    match self.0 {
      Some(v) => v.to_string(),
      None => "".to_owned(),
    }
  }
}

impl Color {
  pub fn to_rgba(&self) -> Rgba<u8> {
    match self.0 {
      Some([r, g, b]) => Rgba([r, g, b, 255]),
      None => Rgba([255, 255, 255, 255]),
    }
  }
}

impl Size {
  pub fn to_u32(&self) -> u32 {
    match self.0 {
      Some(v) => v,
      None => 0
    }
  }
}

impl Len {
  pub fn to_u32(&self) -> u32 {
    match self.0 {
      Some(v) => v,
      None => 0
    }
  }
}

impl AppPageCaptureSection {
  pub fn from_hashmap(hm: HashMap<String, AppPageCaptureItemValue>) -> Option<Self> {
    if hm.len() == 0 {
      return None;
    }
    Some(AppPageCaptureSection {
      crop: hm.get("crop").into(),
      swipe_from: hm.get("swipe_from").into(),
      swipe_to: hm.get("swipe_to").into(),
      swipe_duration: hm.get("swipe_duration").into(),
      segment_border_color: hm.get("segment_border_color").into(),
      segment_sample_size: hm.get("segment_sample_size").into(),
      segment_left_padding: hm.get("segment_left_padding").into(),
      segment_right_padding: hm.get("segment_right_padding").into(),
      segment_left_crop: hm.get("segment_left_crop").into(),
      segment_right_crop: hm.get("segment_right_crop").into(),
      segment_min_height: hm.get("segment_min_height").into(),
      segment_max_height: hm.get("segment_max_height").into(),
    })
  }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AppPageCaptureItemValue {
  Rect { x: u32, y: u32, w: u32, h: u32 },
  Point { x: u32, y: u32 },
  Rgb { r: u8, g: u8, b: u8 },
  UInt(u32),
}

impl From<u32> for AppPageCaptureItemValue {
  fn from(v: u32) -> Self {
    AppPageCaptureItemValue::UInt(v)
  }
}

impl From<Vec<u32>> for AppPageCaptureItemValue {
  fn from(v: Vec<u32>) -> Self {
    match v.len() {
      4 => AppPageCaptureItemValue::Rect {
        x: v[0],
        y: v[1],
        w: v[2],
        h: v[3],
      },
      3 => AppPageCaptureItemValue::Rgb {
        r: (v[0] % 256) as u8,
        g: (v[1] % 256) as u8,
        b: (v[2] % 256) as u8,
      },
      2 => AppPageCaptureItemValue::Point { x: v[0], y: v[1] },
      _ => AppPageCaptureItemValue::UInt(v[0]),
    }
  }
}

impl FromStr for AppPageCaptureItemValue {
  type Err = <u32 as std::str::FromStr>::Err;
  fn from_str(s: &str) -> Result<Self, Self::Err> {
    let vals = if s.is_empty() {
      Ok(Vec::new())
    } else {
      // let a = s.split(",").map(<u32 as std::str::FromStr>::from_str);
      // let b = a.collect::<std::result::Result<Vec<_>, Self::Err>>();
      // let c = b.map(<Self as std::convert::From<Vec<u32>>>::from);
      s.split(",")
        .map(<u32 as std::str::FromStr>::from_str)
        .collect::<std::result::Result<Vec<_>, Self::Err>>()
    };
    vals.map(<Self as From<Vec<u32>>>::from)
    // match vals {
    //   [] => {},
    //   [val] => {
    //     Ok(AppPageCaptureItemValue::UInt(val))
    //   },
    //   [x, y, w, h] => {
    //     Ok(AppPageCaptureItemValue::(val))
    //   }
    // }
  }
}

impl ProjectConf {
  pub fn new(name: String, app_name: String, description: Option<String>) -> Self {
    ProjectConf {
      project: ProjectSection { name, description },
      app: AppSection::new(app_name),
    }
  }
}

impl<'a, P> Tomlable<'a, P> for ProjectConf
where
  P: AsRef<Path>,
{
  fn write_to_toml(&self, to_location: P, overridable: bool) -> io::Result<()> {
    let config_file = to_location.as_ref();
    if !config_file.exists() || overridable {
      let action = iif!(!overridable, "创建", "覆盖");
      println!("{}project配置文件...", action);
      let toml = toml::to_string(self).unwrap();
      if let Ok(_) = fs::write(config_file, toml) {
        println!("==成功{}project配置文件`{}`==", action, self.project.name);
      } else {
        eprintln!("can't save .toml in the path<{}>", config_file.display());
      }
    } else {
      println!("==已检测到project配置文件,跳过==");
    }

    Ok(())
  }
  fn read_from_toml(from_location: P) -> io::Result<Self> {
    let content = fs::read_to_string(from_location.as_ref())?;
    let proj_conf = toml::from_str::<Self>(&content);

    match proj_conf {
      Ok(proj_conf) => {
        return Ok(proj_conf);
      }
      Err(err) => {
        eprint!("err=>{}", err);
        panic!(
          "解析project配置文件<{}>出错",
          from_location.as_ref().display()
        );
      }
    }
  }
}
